/**
 * The MIT License
 *
 * Copyright (c) 2010 Benjamin Klum
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package org.helgoboss.obr_facade_ref_impl

import org.osgi.service.obr.RepositoryAdmin
import org.osgi.service.packageadmin.PackageAdmin
import org.helgoboss.osgi_additions._
import org.osgi.framework.{Bundle, BundleActivator, BundleContext}
import org.helgoboss.obr_facade.{ObrFacade, Resource, ResourceId}
import org.helgoboss.scala_additions.RichFile
import org.helgoboss.scala_additions.Logger
import org.helgoboss.scala_additions.Implicits._
import java.net.URL

class Activator extends OsgiContext with OsgiLogging with OsgiServiceWatcher with OsgiProvider {
    whenBundleActive {
        whenServicesPresent[RepositoryAdmin, PackageAdmin] {
            new ObrFacadeRefImpl(_, _, bundleContext, log).providesService[ObrFacade]
        }
    }
}   


class ObrFacadeRefImpl(repositoryAdmin: RepositoryAdmin, packageAdmin: PackageAdmin, bundleContext: BundleContext, log: Logger) extends ObrFacade {
    def installedRepositories = repositoryAdmin.listRepositories.toList
    
    def installedRemoteRepositories = installedRepositories.filter(_.getURL.getProtocol != "file")
        
    def installedLocalRepositories = installedRepositories.filter(_.getURL.getProtocol == "file")

    def findInstalledRepository(repositoryUrl: URL) = {
        repositoryAdmin.listRepositories.find(_.getURL == repositoryUrl)
    }
    
    
    def installOrRefreshRepository(repositoryUrl: URL) {
        findInstalledRepository(repositoryUrl).map(_.getURL).foreach(uninstallRepository)
        installRepository(repositoryUrl)
    }
    
    def developmentRepositoryUrl = {
        val expectedFile = RichFile.userHomeDir / ".m2/repository/repository.xml"
        if (expectedFile.exists) {
            Some(expectedFile.toURI.toURL)
        } else {
            None
        }
    }
    
    def installRepository(repositoryUrl: URL) {
        repositoryAdmin.addRepository(repositoryUrl)
    }
    
    def uninstallRepository(repositoryUrl: URL) {
        repositoryAdmin.removeRepository(repositoryUrl)
    }
    
    def switchOnOrRefreshResource(resourceId: ResourceId) {
        if (resourceIsSwitchedOn(resourceId)) {
            switchOffResource(resourceId)
        }
        switchOnResource(resourceId)
    }

    // TODO support real filter expression
    def findResources(filterExpression: String) = {
        /* Put installed bundles in fast to query hash map */
        
        val installedBundles = new collection.mutable.HashMap[ResourceId, Bundle]
        
        bundleContext.getBundles.foreach { bundle =>
            if (bundle.getSymbolicName.contains(filterExpression)) {
                val key = ResourceId(bundle.getSymbolicName, bundle.getVersion.toString)
                installedBundles += key -> bundle
            }
        }
        
        /* Find repository resources */
        
        // TODO support real filter expression
        val obrResources = repositoryAdmin.discoverResources(buildFilterString(List(ResourceId("*" + filterExpression + "*", "*"))))
        
        
        /* Map resources to installed bundles */
        
        val resources = new collection.mutable.ListBuffer[Resource]
        
        val resourcesWithRepository = obrResources.map { obrResource =>
            val key = ResourceId(obrResource.getSymbolicName, obrResource.getVersion.toString)
            val optionalBundle = installedBundles.get(key) match {
                case Some(bundle) =>
                    installedBundles -= key
                    Some(bundle)
                    
                case None => 
                    None
            }
            new ResourceImpl(obrResource = Some(obrResource), bundle = optionalBundle)
        }
        
        
        /* Add remaining bundles without corresponding resource */
        
        val resourcesWithoutRepository = installedBundles.values.map { bundle =>
            new ResourceImpl(obrResource = None, bundle = Some(bundle))
        }
        
        resourcesWithRepository.toList ++ resourcesWithoutRepository
    }

    def findResource(resourceId: ResourceId): Option[Resource] = {
        findResources(buildFilterString(List(resourceId))).headOption
    }


    def resourceIsSwitchedOn(resourceId: ResourceId) = {
        val bundles = packageAdmin.getBundles(resourceId.symbolicName, resourceId.version)
        if (bundles == null) {
            false
        } else {
            import Bundle._
            bundles(0).getState match {
                case STARTING | ACTIVE | STOPPING => true
                case _ => false
            }
        }
    }
    
    def switchOnResource(resourceId: ResourceId) {
        switchOnResources(List(resourceId))
    }
    
    def switchOffResource(resourceId: ResourceId) {
        val bundles = packageAdmin.getBundles(resourceId.symbolicName, resourceId.version)
        if (bundles != null) {
            bundles.foreach(_.uninstall)
        }
    }
    
    def switchOnResources(resourceIds: Iterable[ResourceId]) {
        val resolver = repositoryAdmin.resolver
        val filterString = buildFilterString(resourceIds)
        val resources = repositoryAdmin.discoverResources(filterString)
        if (resources != null && resources.size > 0) {
            resources.foreach(resolver.add)
            if (resolver.resolve) {
                resolver.deploy(true);
            } else {
                val unsatisfiedRequirementsString = resolver.getUnsatisfiedRequirements.map(_.getFilter).mkString(", ")
                error("Could not resolve [" + filterString + "] because requirements [" + unsatisfiedRequirementsString + "] are not satisfied")
            }
        } else {
            error("Could not discover [" + filterString + "]")
        }
        
        /* If we wanted to switch on several resources and they were successfully installed, it can
         * still happen, that some of them didn't start successfully. In that case, subsequent resources
         * are not started anymore. We attempt to do this manually.
         */
        if (resourceIds.size > 0) {
            resourceIds.foreach { id =>
                val bundles = packageAdmin.getBundles(id.symbolicName, id.version)
                if (bundles == null) {
                    log.debug("Bundle [" + id + "] not installed")
                } else {
                    bundles.foreach { bundle =>
                        if (bundle.getState == Bundle.ACTIVE) {
                            log.debug("Bundle [" + id + "] active")
                        } else {
                            log.debug("Bundle [" + id + "] inactive. Try to start it.")
                            bundle.start(0)
                        }
                    }
                }
            }
        }
    }
    
    
    
    private def buildFilterString(resourceIds: Iterable[ResourceId]) = {
        resourceIds.map { id =>
            "(&(symbolicname=" + id.symbolicName + ")(version=" + id.version + "))"
        }.mkString("(|", "", ")")
    }
}